﻿#include "pch.h"
#include "ShellExecuteHelper.h"

#include <iomanip>
#include <sstream>
#include <shlobj.h>



ShellExecuteHelper::ShellExecuteHelper()
{
}


ShellExecuteHelper::~ShellExecuteHelper()
{
}

DWORD ShellExecuteHelper::GetSystemErrorCodeMessage(DWORD errCode, std::string & errorMessage)
{
    DWORD lastSystemError = 0;
    DWORD ret = 0;
    DWORD flags = 0;
    LPVOID lpMsgBuf = NULL;
    
    flags |= FORMAT_MESSAGE_ALLOCATE_BUFFER;

    // lpSource: Pointer to a string that consists of unformatted message text. 
    // It will be scanned for inserts and formatted accordingly.
    //flags |= FORMAT_MESSAGE_FROM_STRING;
    //flags |= FORMAT_MESSAGE_FROM_HMODULE; //  lpSource 参数传入模块句柄 在模块中搜索到所需消息
    flags |= FORMAT_MESSAGE_FROM_SYSTEM;
    flags |= FORMAT_MESSAGE_IGNORE_INSERTS;  // 如果这个标示符设置好了，那么Arguments参数将被忽略。
    //flags |= FORMAT_MESSAGE_MAX_WIDTH_MASK; // 如果没有这个设置，那么得到的消息包含换行符 "\r\n"，设置了这个，就没有了

    ret = FormatMessageA(flags, NULL, 
        errCode,
        MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
        //MAKELANGID(LANG_CHINESE_SIMPLIFIED, SUBLANG_CHINESE_SIMPLIFIED),
        (LPSTR)&lpMsgBuf, 0, NULL);
    if (ret <= 0) {
        DWORD lastSystemError = GetLastError();
        std::stringstream ss;
        ss << "FormatMessageA失败，" << "GetLastError()=" << lastSystemError << "，原错误码( " << errCode << " )";
        errorMessage = ss.str();
    }
    else {
        errorMessage = (LPCSTR)lpMsgBuf;
    }

    if (lpMsgBuf) {
        LocalFree(lpMsgBuf);
        lpMsgBuf = NULL;
    }
    return lastSystemError;
}

BOOL ShellExecuteHelper::GetModuleExecutable(OUT std::string & executable)
{
    CHAR path[MAX_PATH];
    GetModuleFileNameA(NULL, path, MAX_PATH);
    executable = path;
    return TRUE;
}

BOOL ShellExecuteHelper::GetModuleDirectory(OUT std::string & directory)
{
    CHAR path[MAX_PATH];
    GetModuleFileNameA(NULL, path, MAX_PATH);
    LPSTR ptr = strrchr(path, '\\');//[0] = 0;
    if (ptr == NULL) {
        return FALSE;
    }
    ptr[0] = 0;
    directory = path;
    return TRUE;
}

BOOL ShellExecuteHelper::GetSystemErrorCodeFromHinst(HINSTANCE hInstApp, DWORD& errorCode)
{
    BOOL bTrue = TRUE;

    switch ((DWORD)hInstApp)
    {
    case SE_ERR_FNF:
        errorCode = ERROR_FILE_NOT_FOUND;
        break;
    case SE_ERR_PNF:
        errorCode = ERROR_PATH_NOT_FOUND;
        break;
    case SE_ERR_ACCESSDENIED:
        errorCode = ERROR_ACCESS_DENIED;
        break;
    case SE_ERR_OOM:
        errorCode = ERROR_NOT_ENOUGH_MEMORY;
        break;
    case SE_ERR_DLLNOTFOUND:
        errorCode = ERROR_DLL_NOT_FOUND;
        break;
    case SE_ERR_SHARE:
        errorCode = ERROR_SHARING_VIOLATION; /*  待验证 */
        break;
    case SE_ERR_ASSOCINCOMPLETE:
        errorCode = ERROR_NO_ASSOCIATION; /*  待验证 */
        break;
    case SE_ERR_NOASSOC:
        errorCode = ERROR_NO_ASSOCIATION; /*  待验证 */
        break;
    case SE_ERR_DDETIMEOUT:
    case SE_ERR_DDEFAIL:
    case SE_ERR_DDEBUSY:
        errorCode = ERROR_DDE_FAIL; /*  待验证 */
        break;
    default:
        bTrue = FALSE;
        break;
    }
    return bTrue;
}

std::string ShellExecuteHelper::GetErrTextFromHinst(HINSTANCE hInstApp)
{
    std::string errmsg;

    if ((int)hInstApp == SE_ERR_FNF) {
        errmsg = "文件没有找到";
    }
    else if ((int)hInstApp == SE_ERR_PNF) {
        errmsg = "路径没有找到";
    }
    else if ((int)hInstApp == SE_ERR_ACCESSDENIED) {
        errmsg = "访问被拒绝";
    }
    else if ((int)hInstApp == SE_ERR_OOM) {
        errmsg = "内存不足";//Out of memory.
    }
    else if ((int)hInstApp == SE_ERR_DLLNOTFOUND) {
        errmsg = "动态链接库没有找到";//Dynamic-link library not found.
    }
    else if ((int)hInstApp == SE_ERR_SHARE) {
        errmsg = "无法共享打开的文件";//Cannot share an open file
    }
    else if ((int)hInstApp == SE_ERR_ASSOCINCOMPLETE) {
        errmsg = "文件关联信息不完整";//_T("File association information not complete.");
    }
    else if ((int)hInstApp == SE_ERR_DDETIMEOUT) {
        errmsg = "DDE操作超时";
    }
    else if ((int)hInstApp == SE_ERR_DDEFAIL) {
        errmsg = "动态数据交换(DDE)事务失败";
    }
    else if ((int)hInstApp == SE_ERR_DDEBUSY) {
        errmsg = "DDE操作忙";
    }
    else if ((int)hInstApp == SE_ERR_NOASSOC) {
        errmsg = "文件关联不可用";//_T("File association not available.")
    }
    else {
        std::stringstream ss;
        //ss << "未知错误, Code = 0x" << std::setw(8) << std::setfill('0') << std::hex << (int)hInstApp;
        ss << "未知错误, Code=" << (int)hInstApp;
        errmsg = ss.str();
    }
    return errmsg;
}

DWORD ShellExecuteHelper::LaunchFromProgramWithParameter(const char * executable, const char * parameters, const char * workDirectory, const char* verb, std::string* erroeMessage)
{
    DWORD lastSystemError = ERROR_SUCCESS;
    BOOL bTrue = FALSE;
    SHELLEXECUTEINFOA sei;

    do
    {
        memset(&sei, 0, sizeof(sei));
        sei.cbSize = sizeof(sei);
        sei.fMask |= SEE_MASK_FLAG_NO_UI;
        sei.fMask |= SEE_MASK_NO_CONSOLE;
        sei.fMask |= SEE_MASK_NOCLOSEPROCESS;

        sei.lpVerb = verb;
        sei.lpFile = executable;
        sei.lpParameters = parameters;
        sei.lpDirectory = workDirectory;
        sei.nShow = SW_SHOWNORMAL;// SW_SHOW;

        // [2021-01-31] 该函数有时候需要COM环境 
        //// CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
        bTrue = ShellExecuteExA(&sei);
        if (!bTrue) {
            lastSystemError = GetLastError();
            // CO_E_APPNOTFOUND 0x800401F5
            // ERROR_NO_ASSOCIATION   1155L
            DWORD err = 0;
            std::string errmsg;
            
            if (ERROR_CANCELLED == lastSystemError) {
                errmsg = "用户取消";
            }
            else {
                err = GetSystemErrorCodeMessage(lastSystemError, errmsg);
            }

            if (ERROR_SUCCESS == err && erroeMessage)
                *erroeMessage = errmsg;
            break;
        }

        if (sei.hProcess != NULL) {
            // 类似 QQPCFileOpen.exe 这样的程序会启动
            // 出现一个界面，让你选择用哪个程序打开
            // 
            // 在这儿注册
            // HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Unknown\shell\Open
            DWORD thrdId = GetProcessId(sei.hProcess);
            CloseHandle(sei.hProcess);
        }

        lastSystemError = ERROR_SUCCESS;
    } while (false);

    return lastSystemError;
}

BOOL ShellExecuteHelper::GetProcessElevation(TOKEN_ELEVATION_TYPE * pElevationType, BOOL * pIsAdmin)
{
    BOOL bTrue = FALSE;
    HANDLE hToken = NULL;
    DWORD dwSize = 0;
    DWORD lastSystemError = 0;
    BYTE adminSID[SECURITY_MAX_SID_SIZE];
    
    do
    {
        bTrue = OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken);
        if (!bTrue) {
            lastSystemError = GetLastError();
            break;
        }

        bTrue = GetTokenInformation(hToken, TokenElevationType,
            pElevationType, sizeof(TOKEN_ELEVATION_TYPE), &dwSize);
        if (!bTrue) {
            lastSystemError = GetLastError();
            break;
        }

        dwSize = sizeof(adminSID);
        bTrue = CreateWellKnownSid(WinBuiltinAdministratorsSid, 
            NULL, &adminSID, &dwSize);
        if (!bTrue) {
            lastSystemError = GetLastError();
            break;
        }

        if (*pElevationType == TokenElevationTypeLimited) {

            HANDLE hUnfilteredToken = NULL;

            bTrue = GetTokenInformation(hToken, TokenLinkedToken, 
                (void*) &hUnfilteredToken, sizeof(HANDLE), &dwSize);
            if (!bTrue) {
                lastSystemError = GetLastError();
                break;
            }

            bTrue = CheckTokenMembership(hUnfilteredToken, &adminSID, pIsAdmin);
            lastSystemError = GetLastError();
            CloseHandle(hUnfilteredToken);
        }
        else {
            *pIsAdmin = IsUserAnAdmin();
            bTrue = TRUE;
        }

    } while (false);
    
    if (hToken)
        CloseHandle(hToken);

    if (!bTrue) SetLastError(lastSystemError);

    return bTrue;
}
